package play.modules.cream.ocm; import java.lang.reflect.InvocationTargetException; import java.util.ArrayList; import java.util.Calendar; import java.util.List; import javax.jcr.Node; import javax.jcr.NodeIterator; import javax.jcr.RepositoryException; import javax.jcr.Session; import javax.jcr.nodetype.NodeType; import javax.jcr.query.Query; import javax.jcr.query.QueryManager; import javax.jcr.query.QueryResult; import javax.jcr.version.VersionManager; import org.jcrom.JcrMappingException; import org.jcrom.Jcrom; import play.Play; import play.exceptions.UnexpectedException; import play.modules.cream.JCR; import play.modules.cream.JcrMetadata; import play.modules.cream.JcrMetadata.MD; import play.modules.cream.Model; import play.modules.cream.helpers.JcrUtils; import play.modules.cream.helpers.NullAwareBeanUtilsBean; // TODO replace some strategic exceptions with return null @SuppressWarnings("unchecked") public class JcrMapper { // Jcrom is thread-safe public static Jcrom jcrom; public static NullAwareBeanUtilsBean beanUtils = new NullAwareBeanUtilsBean(); public static Node addNode(Node parentNode, Object entity) throws JcrMappingException { return jcrom.addNode(parentNode, entity); } public static Node addNode(Node arg0, Object arg1, String[] arg2) throws JcrMappingException { return jcrom.addNode(arg0, arg1, arg2); } public static <T> T create(String parentNodePath, T entity) { try { MD md = getMetadata(entity.getClass()); String entityName = jcrom.getName(entity); if (entityName == null || entityName.equals("")) { throw new JcrMappingException("The name of the entity being created is empty!"); } if (parentNodePath == null || parentNodePath.equals("")) { throw new JcrMappingException("The parent path of the entity being created is empty!"); } Node parentNode; Session session = getSession(); Node rootNode = session.getRootNode(); if (parentNodePath.equals("/")) { // special case, add directly to the root node parentNode = rootNode; } else { String relativePath = relativePath(parentNodePath); if (rootNode.hasNode(relativePath)) { parentNode = rootNode.getNode(relativePath); } else { // if not found create it parentNode = rootNode.addNode(relativePath); } } Node newNode = jcrom.addNode(parentNode, entity, md.mixinTypes); session.save(); if (md.isVersionable) { checkinRecursively(session.getWorkspace().getVersionManager(), newNode); } return entity; } catch (RepositoryException e) { throw new JcrMappingException("Could not create node", e); } } public static <T> T create(T entity) { return create(jcrom.getPath(entity), entity); } public static boolean exists(String path) { try { return getSession().getRootNode().hasNode(relativePath(path)); } catch (RepositoryException e) { throw new JcrMappingException("Could not check if node exists", e); } } public static <T> JcrQueryResult<T> find(final String className, final String queryString, Object... params) throws RepositoryException { Class<T> clazz = Play.classloader.getClassIgnoreCase(className); return executeQuery(clazz, queryString, params); } public static <T> JcrQueryResult<T> findAll(Class<T> clazz, String rootPath) { return findAll(clazz, rootPath, "*", -1); } public static <T> JcrQueryResult<T> findAll(Class<T> clazz, String rootPath, String childNameFilter, int maxDepth) { try { NodeIterator nodeIterator = getSession().getRootNode().getNode(JcrMapper.relativePath(rootPath)).getNodes(); return toJcrQuery(clazz, nodeIterator); } catch (RepositoryException e) { throw new JcrMappingException("Could not find nodes", e); } } public static <T> JcrQueryResult<T> findAll(String className) { Class<T> clazz = Play.classloader.getClassIgnoreCase(className); return findAll(clazz, getDefaultPath(clazz)); } public static <T> JcrQueryResult<T> findAll(String className, String rootPath) { return findAll(Play.classloader.getClassIgnoreCase(className), rootPath); } public static <T> JcrQueryResult<T> findBy(final Class<T> clazz, final String path, final String where, Object... params) throws RepositoryException { String nodeType = getMetadata(clazz).nodeType; String queryString = JcrUtils.buildSelect(path, where, nodeType); return executeQuery(clazz, queryString, params); } public static <T> JcrQueryResult<T> findBy(final String className, final String where, Object... params) throws RepositoryException { Class<T> clazz = Play.classloader.getClassIgnoreCase(className); return findBy(clazz, getDefaultPath(clazz), where, params); } public static <T> JcrQueryResult<T> findByPath(final String className, final String path, final String where, Object... params) throws RepositoryException { Class<T> clazz = Play.classloader.getClassIgnoreCase(className); return findBy(clazz, path, where, params); } public static <T> T fromNode(Class<T> entityClass, Node node) throws JcrMappingException { return jcrom.fromNode(entityClass, node); } public static <T> T fromNode(Class<T> entityClass, Node node, String childNodeFilter, int maxDepth) throws JcrMappingException { return jcrom.fromNode(entityClass, node, childNodeFilter, maxDepth); } public static <T> T get(Class<T> clazz, String path) { return get(clazz, path, "*", -1); } public static <T> T get(Class<T> clazz, String path, String childNodeFilter, int maxDepth) { if (exists(path)) { Node node; try { node = getSession().getRootNode().getNode(relativePath(path)); } catch (RepositoryException e) { throw new JcrMappingException("Could not get node", e); } return jcrom.fromNode(clazz, node, childNodeFilter, maxDepth); } else { return null; } } @SuppressWarnings("unchecked") public static <T extends Model> T get(String className) { Class<T> clazz = Play.classloader.getClassIgnoreCase(className); return get(clazz, getDefaultPath(clazz)); } @SuppressWarnings("unchecked") public static <T extends Model> T get(String className, String path) { Class<T> clazz = Play.classloader.getClassIgnoreCase(className); return get(clazz, path); } public static String getDefaultPath(Class<?> modelClass) { return "/" + modelClass.getSimpleName().toLowerCase(); } public static String getName(Object arg0) throws JcrMappingException { return jcrom.getName(arg0); } public static String getPath(Object arg0) throws JcrMappingException { return jcrom.getPath(arg0); } public static long getSize(String rootPath) { try { NodeIterator nodeIterator = getSession().getRootNode().getNode(relativePath(rootPath)).getNodes(); return nodeIterator.getSize(); } catch (RepositoryException e) { throw new JcrMappingException("Could not get list size", e); } } public static boolean isMapped(Class entityClass) { return jcrom.isMapped(entityClass); } public static <T> T loadByUUID(Class<T> clazz, String uuid) { return loadByUUID(clazz, uuid, "*", -1); } public static <T> T loadByUUID(Class<T> clazz, String uuid, String childNodeFilter, int maxDepth) { Node node; try { node = getSession().getNodeByIdentifier(uuid); } catch (RepositoryException e) { // throw new JcrMappingException("Could not load node", e); return null; } return jcrom.fromNode(clazz, node, childNodeFilter, maxDepth); } @SuppressWarnings("unchecked") public static <T extends Model> T loadByUUID(String className, String uuid) { return (T) loadByUUID(Play.classloader.getClassIgnoreCase(className), uuid); } public static void map(Class classToMap) { jcrom.map(classToMap); } public static <T extends Model> T merge(T entity) { return merge(entity, "*", -1); } public static <T extends Model> T merge(T entity, String childNodeFilter, int maxDepth) { T original = (T) loadByUUID(entity.getClass(), entity.uuid); try { beanUtils.copyProperties(original, entity); } catch (IllegalAccessException e) { throw new UnexpectedException(e); } catch (InvocationTargetException e) { throw new UnexpectedException(e); } return update(original, childNodeFilter, maxDepth); } public static <T> T move(T entity, String newParentPath) { // if this is a versionable node, then we need to check out both // the old parent and the new parent before moving the node try { String sourcePath = jcrom.getPath(entity); String entityName = jcrom.getName(entity); Node oldParent = null; Node newParent = null; MD md = getMetadata(entity.getClass()); Session session = getSession(); VersionManager versionManager = null; if (md.isVersionable) { versionManager = session.getWorkspace().getVersionManager(); oldParent = session.getRootNode().getNode(relativePath(sourcePath)).getParent(); newParent = newParentPath.equals("/") ? session.getRootNode() : session.getRootNode().getNode( relativePath(newParentPath)); if (hasMixinType(oldParent, "mix:versionable")) { versionManager.checkout(oldParent.getPath()); } if (hasMixinType(newParent, "mix:versionable")) { versionManager.checkout(newParent.getPath()); } } if (newParentPath.equals("/")) { // special case, moving to root session.move(sourcePath, newParentPath + entityName); } else { session.move(sourcePath, newParentPath + "/" + entityName); } session.save(); if (md.isVersionable) { if (hasMixinType(oldParent, "mix:versionable") && oldParent.isCheckedOut()) { versionManager.checkin(oldParent.getPath()); } if (hasMixinType(newParent, "mix:versionable") && newParent.isCheckedOut()) { versionManager.checkin(newParent.getPath()); } } } catch (RepositoryException e) { throw new JcrMappingException("Could not move node", e); } return entity; } public static void remove(Model model) { remove(jcrom.getPath(model), model.getClass()); } public static void remove(String path, Class<?> clazz) { try { Node parent = null; MD md = getMetadata(clazz); Session session = getSession(); VersionManager versionManager = null; if (md.isVersionable) { versionManager = getSession().getWorkspace().getVersionManager(); parent = session.getRootNode().getNode(relativePath(path)).getParent(); if (hasMixinType(parent, "mix:versionable")) { versionManager.checkout(parent.getPath()); } } session.getRootNode().getNode(relativePath(path)).remove(); session.save(); if (md.isVersionable) { if (hasMixinType(parent, "mix:versionable") && parent.isCheckedOut()) { versionManager.checkin(parent.getPath()); } } } catch (RepositoryException e) { throw new JcrMappingException("Could not remove node", e); } } public static void removeByUUID(String uuid, Class<?> clazz) { try { Session session = getSession(); Node node = session.getNodeByIdentifier(uuid); Node parent = null; MD md = getMetadata(clazz); VersionManager versionManager = null; if (md.isVersionable) { versionManager = session.getWorkspace().getVersionManager(); parent = node.getParent(); if (hasMixinType(parent, "mix:versionable")) { versionManager.checkout(parent.getPath()); } } node.remove(); session.save(); if (md.isVersionable) { if (hasMixinType(parent, "mix:versionable") && parent.isCheckedOut()) { versionManager.checkin(parent.getPath()); } } } catch (RepositoryException e) { throw new JcrMappingException("Could not remove node", e); } } public static void setBaseVersionInfo(Object object, String name, Calendar created) { jcrom.setBaseVersionInfo(object, name, created); } /** * Maps JCR nodes to a List of JcrEntity implementations. * * @param nodeIterator * the iterator pointing to the nodes * @param childNameFilter * comma separated list of names of child nodes to load ("*" * loads all, "none" loads no children, and "-" at the beginning * makes it an exclusion filter) * @param maxDepth * the maximum depth of loaded child nodes (0 means no child * nodes are loaded, while a negative value means that no * restrictions are set on the depth). * @return a list of objects mapped from the nodes */ public static <T> List<T> toList(Class<T> clazz, NodeIterator nodeIterator, String childNameFilter, int maxDepth) { List<T> objects = new ArrayList<T>(); while (nodeIterator.hasNext()) { objects.add(jcrom.fromNode(clazz, nodeIterator.nextNode(), childNameFilter, maxDepth)); } return objects; } /** * Maps JCR nodes to a List of JcrEntity implementations. * * @param nodeIterator * the iterator pointing to the nodes * @param childNameFilter * comma separated list of names of child nodes to load ("*" * loads all, "none" loads no children, and "-" at the beginning * makes it an exclusion filter) * @param maxDepth * the maximum depth of loaded child nodes (0 means no child * nodes are loaded, while a negative value means that no * restrictions are set on the depth). * @param resultSize * the number of items to retrieve from the iterator * @return a list of objects mapped from the nodes */ public static <T> List<T> toList(Class<T> clazz, NodeIterator nodeIterator, String childNameFilter, int maxDepth, long resultSize) { List<T> objects = new ArrayList<T>(); long counter = 0; while (nodeIterator.hasNext()) { if (counter == resultSize) { break; } objects.add(jcrom.fromNode(clazz, nodeIterator.nextNode(), childNameFilter, maxDepth)); counter++; } return objects; } public static <T> T update(T entity) { return update(entity, "*", -1); } public static <T> T update(T entity, String childNodeFilter, int maxDepth) { Node node; try { node = getSession().getRootNode().getNode(relativePath(jcrom.getPath(entity))); } catch (RepositoryException e) { throw new JcrMappingException("Could not update node", e); } return update(node, entity, childNodeFilter, maxDepth); } public static <T> T updateByUUID(T entity, String uuid) { return updateByUUID(entity, uuid, "*", -1); } public static <T> T updateByUUID(T entity, String uuid, String childNodeFilter, int maxDepth) { Node node; try { node = getSession().getNodeByIdentifier(uuid); } catch (RepositoryException e) { throw new JcrMappingException("Could not update node", e); } return update(node, entity, childNodeFilter, maxDepth); } public static String updateNode(Node node, Object entity) throws JcrMappingException { return jcrom.updateNode(node, entity); } public static String updateNode(Node node, Object entity, String arg2, int arg3) throws JcrMappingException { return jcrom.updateNode(node, entity, arg2, arg3); } protected static <T> JcrQueryResult<T> executeQuery(Class<T> clazz, String queryString, Object... params) throws RepositoryException { QueryManager queryManager = JCR.getQueryManager(); Query query = queryManager.createQuery( (params == null) ? queryString : JcrUtils.queryFormat(queryString, params), Query.JCR_SQL2); QueryResult queryResult = query.execute(); NodeIterator nodeItor = queryResult.getNodes(); return toJcrQuery(clazz, nodeItor); } protected static MD getMetadata(Class<?> clazz) { return JcrMetadata.getInstance().lookup(clazz); } protected static Session getSession() { return JCR.getSession(); } protected static boolean hasMixinType(Node node, String mixinType) throws RepositoryException { for (NodeType nodeType : node.getMixinNodeTypes()) { if (nodeType.getName().equals(mixinType)) { return true; } } return false; } protected static String relativePath(String absolutePath) { if (absolutePath.startsWith("/")) { return absolutePath.substring(1); } else { return absolutePath; } } protected static <T> JcrQueryResult<T> toJcrQuery(final Class<T> clazz, NodeIterator nodeIterator) { return new JcrQueryResult<T>(clazz, nodeIterator); } protected static <T> T update(Node node, T entity, String childNodeFilter, int maxDepth) { try { Session session = getSession(); VersionManager versionManager = null; MD md = getMetadata(entity.getClass()); if (md.isVersionable) { versionManager = session.getWorkspace().getVersionManager(); checkoutRecursively(versionManager, node); } jcrom.updateNode(node, entity, childNodeFilter, maxDepth); session.save(); if (md.isVersionable) { checkinRecursively(versionManager, node); } return entity; } catch (RepositoryException e) { throw new JcrMappingException("Could not update node", e); } } private static void checkinRecursively(VersionManager versionManager, Node node) { try { NodeIterator it = node.getNodes(); while (it.hasNext()) { checkinRecursively(versionManager, it.nextNode()); } if (node.isCheckedOut() && node.isNodeType("mix:versionable")) { versionManager.checkin(node.getPath()); } } catch (RepositoryException e) { throw new JcrMappingException("Could not perform check-in", e); } } private static void checkoutRecursively(VersionManager versionManager, Node node) { try { NodeIterator it = node.getNodes(); while (it.hasNext()) { checkoutRecursively(versionManager, it.nextNode()); } if (!node.isCheckedOut() && node.isNodeType("mix:versionable")) { versionManager.checkout(node.getPath()); } } catch (RepositoryException e) { throw new JcrMappingException("Could not perform check-in", e); } } private JcrMapper() { } }